package com.wsoft.utils;

import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.wsoft.config.ResponseBodyContext;
import com.wsoft.constant.CommonConstant;
import com.wsoft.vo.LogVO;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.InputStreamResource;

import java.lang.reflect.Method;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Author z001
 * @Date 2024/11/21 08:52
 */
public class LogUtil {


    /**
     * 保存日志最大长度
     */
    public static final int LOG_MAX_LENGTH = 2000;

    /**
     * 拦截器 preHandle 方法 , 获取存储日志相关信息
     *
     * @param request
     * @param response
     * @param handler
     * @param applicationContext
     * @throws Exception
     */
    public static void preHandle(LogVO logVO,
                                 Method method, ApplicationContext applicationContext) throws Exception {

        //自由表单的日志额外进行可读性转换
        String designName = buildFormLog(logVO, applicationContext);
        //方法路径
        logVO.setMethod(method.getDeclaringClass().getName() + StrUtil.DOT + method.getName());
        // 获取 Swagger @Operation 注解的 summary 和 description
        Operation operation = method.getAnnotation(Operation.class);
        if (operation != null) {
            String title = operation.summary();
            if (StrUtil.isNotBlank(designName)) {
                title = title + StrUtil.DASHED + designName;
            }
            logVO.setTitle(title);
            logVO.setRemark(operation.description());
        }
        ResponseBodyContext.setSysLogInfo(logVO);
    }


    /**
     * 拦截器 afterCompletion 方法,获取存储日志相关方法
     *
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @return
     * @throws Exception
     */
    public static LogVO afterCompletion(Exception ex)  {
        //从自定义上下文中 获取响应体数据
        Object responseBody = ResponseBodyContext.getResponseBody();
        LogVO logVO = (LogVO) ResponseBodyContext.getSysLogInfo();
        if (ObjUtil.isNotEmpty(logVO)) {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS");
            LocalDateTime localDateTime = LocalDateTime.parse(logVO.getCreateTime(), formatter);
            logVO.setRuntime(calculateIntervalInMilliseconds(localDateTime));
            if (responseBody instanceof InputStreamResource) {
                //返回到的流 则不记录日志
                logVO.setResultJson(null);
            }else{
                logVO.setResultJson(truncateString(JSONObject.toJSONString(responseBody), LOG_MAX_LENGTH));
            }

            logVO.setIsException( ex != null ? 1 : 0);
            if (ex != null) {
                logVO.setExceptionInfo(truncateString(ex.getMessage(), LOG_MAX_LENGTH));
            }
        }
        return logVO;
    }


    // ################################## 私有方法  ##################################


    /**
     * 截取保存数据库最大长度
     *
     * @param str
     * @param maxLength
     * @return
     */
    public static String truncateString(String str, int maxLength) {
        if (str == null || str.length() <= maxLength) {
            return str;
        }
        return str.substring(0, maxLength);
    }

    /**
     * 获取当前时间的毫秒值
     */
    public static LocalDateTime getNowTime() {
        // 获取当前时间的毫秒值
        long currentTimeMillis = System.currentTimeMillis();
        // 将毫秒值转换为 Instant 对象
        Instant instant = Instant.ofEpochMilli(currentTimeMillis);
        // 将 Instant 对象转换为 LocalDateTime 对象，使用系统默认时区
        return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
    }

    /**
     * 计算 givenDateTime 和 当前时间的 时间差 单位毫秒
     *
     * @param givenDateTime
     * @return
     */
    private static long calculateIntervalInMilliseconds(LocalDateTime givenDateTime) {
        // 获取当前的 LocalDateTime 对象
        LocalDateTime now = getNowTime();

        // 将给定的 LocalDateTime 对象转换为 Instant 对象
        long givenDateTimeMillis = givenDateTime.toInstant(ZoneOffset.UTC).toEpochMilli();

        // 将当前的 LocalDateTime 对象转换为 Instant 对象
        long nowMillis = now.toInstant(ZoneOffset.UTC).toEpochMilli();

        // 计算时间间隔（以毫秒为单位）
        return nowMillis - givenDateTimeMillis;
    }


    /**
     * 自由表单的日志额外进行可读性转换
     *
     * @param method
     * @param param
     * @return
     * @throws Exception
     */
    private static String buildFormLog(LogVO logVO, ApplicationContext applicationContext) throws Exception {
        String param = JSONObject.toJSONString(logVO.getParamsJson());
        String uri = logVO.getUrl();
        String designId = StrUtil.EMPTY;
        if (uri.contains(CommonConstant.LOG_FORM_URL)) {
            if (CommonConstant.HTTP_GET.equalsIgnoreCase(logVO.getMethodType())) {
                // 定义正则表达式
                Pattern pattern = Pattern.compile(CommonConstant.LOG_DESIGN_ID_REGEX);
                Matcher matcher = pattern.matcher(param);
                if (matcher.find()) {
                    designId = matcher.group(1);
                }
                if (StrUtil.isNotBlank(logVO.getParamsJson())) {
                    logVO.setParamsJson(uri + "?" + logVO.getParamsJson());
                }
            } else {
                param = StrUtil.replace(param, "\\t", "");
                param = StrUtil.replace(param, "\\", "");
                param = param.substring(1, param.length() - 1);
                Map<String, String> map1 = JSON.parseObject(param, new TypeReference<Map<String, String>>() {
                });
                designId = map1.get(CommonConstant.LOG_DESIGN_ID);
            }

            if (StrUtil.isNotBlank(designId)) {
                Object bean = applicationContext.getBean(CommonConstant.LOG_FORM_DESIGN_BEAN_NAME);
                Method method1 = bean.getClass().getMethod(CommonConstant.LOG_FORM_DESIGN_METHOD_NAME, Long.class);
                Object result = method1.invoke(bean, Long.valueOf(designId));
                Map<String, String> map1 = JSON.parseObject(JSONObject.toJSONString(result), new TypeReference<Map<String, String>>() {
                });
                return map1.get(CommonConstant.LOG_FORM_DESIGN_NAME);
            }
        }
        return designId;
    }
}
